//  Copyright 2010 Todd Ditchendorf
//
//  Licensed under the Apache License, Version 2.0 (the "License");
//  you may not use this file except in compliance with the License.
//  You may obtain a copy of the License at
//
//  http://www.apache.org/licenses/LICENSE-2.0
//
//  Unless required by applicable law or agreed to in writing, software
//  distributed under the License is distributed on an "AS IS" BASIS,
//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//  See the License for the specific language governing permissions and
//  limitations under the License.

#import "TDParseTreeTest.h"

@interface PKParserFactory ()
- (NSDictionary *)symbolTableFromGrammar:(NSString *)g error:(NSError **)outError;
@end

@implementation TDParseTreeTest

- (void)setUp {
    factory = [PKParserFactory factory];
    as = [[[PKParseTreeAssembler alloc] init] autorelease];
}


- (void)testAddExpr {
    g = @"@start = expr;"
        @"expr = addExpr;"
        @"addExpr = atom (('+'|'-') atom)*;"
        @"atom = Number;";
    
    PKAST *root = [factory ASTFromGrammar:g error:nil];
    TDEqualObjects(@"(ROOT (@start #expr) ($expr #addExpr) ($addExpr (. #atom (* (. (| '+' '-') #atom)))) ($atom Number))", [root treeDescription]);
    
    NSDictionary *symTab = [factory symbolTableFromGrammar:g error:nil];
    TDNotNil(symTab);
    TDEquals((NSUInteger)4, [symTab count]);
    
    PKSequence *addExprParser = symTab[@"addExpr"];
    TDTrue([addExprParser isKindOfClass:[PKSequence class]]);
    TDTrue(2 == [addExprParser.subparsers count]);
    
    PKNumber *atomNum = addExprParser.subparsers[0];
    TDTrue([atomNum isKindOfClass:[PKNumber class]]);
    
    lp = [factory parserFromGrammar:g assembler:as preassembler:as error:nil];
    
    lp.tokenizer.string = @"1 + 2";
    a = [PKTokenAssembly assemblyWithTokenizer:lp.tokenizer];
    res = [lp completeMatchFor:a];
    TDNotNil(res);
    TDEqualObjects([res description], @"[]1/+/2^");
    
    PKParseTree *tr = res.target;
    TDEqualObjects([tr class], [PKParseTree class]);
    TDEquals([[tr children] count], (NSUInteger)1);
    
    PKRuleNode *expr = [[tr children] objectAtIndex:0];
    TDEqualObjects([expr name], @"expr");
    TDEqualObjects([expr class], [PKRuleNode class]);
    TDEquals([[expr children] count], (NSUInteger)1);
    
    PKRuleNode *addExpr = [[expr children] objectAtIndex:0];
    TDEqualObjects([addExpr name], @"addExpr");
    TDEqualObjects([addExpr class], [PKRuleNode class]);
    TDEquals([[addExpr children] count], (NSUInteger)3);

    PKRuleNode *atom1 = [[addExpr children] objectAtIndex:0];
    TDEqualObjects([atom1 class], [PKRuleNode class]);
    TDEqualObjects([atom1 name], @"atom");
    TDEquals([[atom1 children] count], (NSUInteger)1);

    PKTokenNode *one = [[atom1 children] objectAtIndex:0];
    TDEqualObjects([one class], [PKTokenNode class]);
    TDEqualObjects([one token], [PKToken tokenWithTokenType:PKTokenTypeNumber stringValue:@"1" floatValue:1.0]);

    PKTokenNode *plus = [[addExpr children] objectAtIndex:1];
    TDEqualObjects([plus class], [PKTokenNode class]);
    TDEqualObjects([plus token], [PKToken tokenWithTokenType:PKTokenTypeSymbol stringValue:@"+" floatValue:0]);

    PKRuleNode *atom2 = [[addExpr children] objectAtIndex:2];
    TDEqualObjects([atom2 class], [PKRuleNode class]);
    TDEqualObjects([atom2 name], @"atom");
    TDEquals([[atom2 children] count], (NSUInteger)1);

    PKTokenNode *two = [[atom2 children] objectAtIndex:0];
    TDEqualObjects([two class], [PKTokenNode class]);
    TDEqualObjects([two token], [PKToken tokenWithTokenType:PKTokenTypeNumber stringValue:@"2" floatValue:2.0]);
}


- (void)testFoo {
    g = @"@start = expr;"
    @"expr = Word+;";

    PKAST *root = [factory ASTFromGrammar:g error:nil];
    TDEqualObjects(@"(ROOT (@start #expr) ($expr (+ Word)))", [root treeDescription]);

    NSDictionary *symTab = [factory symbolTableFromGrammar:g error:nil];
    TDNotNil(symTab);
    TDEquals((NSUInteger)2, [symTab count]);

    PKSequence *start = symTab[@"@start"];
    TDTrue([start isKindOfClass:[PKSequence class]]);
    TDTrue(1 == [start.subparsers count]);

    PKSequence *exprParser = symTab[@"expr"];
    TDTrue([exprParser isKindOfClass:[PKSequence class]]);
    TDTrue(2 == [exprParser.subparsers count]);

    TDTrue([exprParser.subparsers[0] isKindOfClass:[PKWord class]]);
    TDTrue([exprParser.subparsers[1] isKindOfClass:[PKRepetition class]]);
    
    PKRepetition *rep = exprParser.subparsers[1];
    TDTrue([rep.subparser isKindOfClass:[PKWord class]]);


    lp = [factory parserFromGrammar:g assembler:as preassembler:as error:nil];
    TDNotNil(lp);
    
    lp.tokenizer.string = @"foo";
    a = [PKTokenAssembly assemblyWithTokenizer:lp.tokenizer];
    res = [lp completeMatchFor:a];
    TDNotNil(res);
    TDEqualObjects([res description], @"[]foo^");
    
    PKParseTree *tr = res.target;
    TDEqualObjects([tr class], [PKParseTree class]);
    TDEquals([[tr children] count], (NSUInteger)1);
    
    PKRuleNode *expr = [[tr children] objectAtIndex:0];
    TDEqualObjects([expr name], @"expr");
    TDEqualObjects([expr class], [PKRuleNode class]);
    TDEquals([[expr children] count], (NSUInteger)1);
    
    PKTokenNode *foo = [[expr children] objectAtIndex:0];
    TDEqualObjects([foo class], [PKTokenNode class]);
    TDEqualObjects([foo token], [PKToken tokenWithTokenType:PKTokenTypeWord stringValue:@"foo" floatValue:0.0]);
}


- (void)testFooBar {
    g = @"@start = expr;"
    @"expr = Word+;";
    lp = [factory parserFromGrammar:g assembler:as preassembler:as error:nil];
    
    lp.tokenizer.string = @"foo bar";
    a = [PKTokenAssembly assemblyWithTokenizer:lp.tokenizer];
    res = [lp completeMatchFor:a];
    TDNotNil(res);
    TDEqualObjects([res description], @"[]foo/bar^");
    
    PKParseTree *tr = res.target;
    TDEqualObjects([tr class], [PKParseTree class]);
    TDEquals([[tr children] count], (NSUInteger)1);
    
    PKRuleNode *expr = [[tr children] objectAtIndex:0];
    TDEqualObjects([expr name], @"expr");
    TDEqualObjects([expr class], [PKRuleNode class]);
    TDEquals([[expr children] count], (NSUInteger)2);
    
    PKTokenNode *foo = [[expr children] objectAtIndex:0];
    TDEqualObjects([foo class], [PKTokenNode class]);
    TDEqualObjects([foo token], [PKToken tokenWithTokenType:PKTokenTypeWord stringValue:@"foo" floatValue:0.0]);

    PKTokenNode *bar = [[expr children] objectAtIndex:1];
    TDEqualObjects([bar class], [PKTokenNode class]);
    TDEqualObjects([bar token], [PKToken tokenWithTokenType:PKTokenTypeWord stringValue:@"bar" floatValue:0.0]);
}


- (void)testArray {
    g = @"@start = array;"
    @"array = '[' Number (commaNumber)* ']';"
    @"commaNumber = ',' Number;";
    
    lp = [factory parserFromGrammar:g assembler:as preassembler:as error:nil];
    
    lp.tokenizer.string = @"[1,2]";
    a = [PKTokenAssembly assemblyWithTokenizer:lp.tokenizer];
    res = [lp completeMatchFor:a];
    TDNotNil(res);
    TDEqualObjects([res description], @"[][/1/,/2/]^");

    PKRuleNode *root = res.target;
    PKRuleNode *array = [[root children] objectAtIndex:0];
    
    TDEqualObjects([array name], @"array");
    TDEqualObjects([array class], [PKRuleNode class]);
    TDEquals([[array children] count], (NSUInteger)4);
    
    PKTokenNode *open = [[array children] objectAtIndex:0];
    TDEqualObjects([open class], [PKTokenNode class]);
    TDEqualObjects([open token], [PKToken tokenWithTokenType:PKTokenTypeSymbol stringValue:@"[" floatValue:0.0]);
    
    PKTokenNode *one = [[array children] objectAtIndex:1];
    TDEqualObjects([one class], [PKTokenNode class]);
    TDEqualObjects([one token], [PKToken tokenWithTokenType:PKTokenTypeNumber stringValue:@"1" floatValue:1.0]);
 
    PKRuleNode *commaNumber = [[array children] objectAtIndex:2];
    TDEqualObjects([commaNumber name], @"commaNumber");
    TDEqualObjects([commaNumber class], [PKRuleNode class]);
    TDEquals([[commaNumber children] count], (NSUInteger)2);
    
    PKTokenNode *close = [[array children] objectAtIndex:3];
    TDEqualObjects([close class], [PKTokenNode class]);
    TDEqualObjects([close token], [PKToken tokenWithTokenType:PKTokenTypeSymbol stringValue:@"]" floatValue:1.0]);
}

@end
